package org.projects.graduates.domain;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import org.apache.commons.lang3.builder.HashCodeBuilder;

import com.dayatang.domain.AbstractEntity;

@Entity
@Table(name = "repeats")
public class Repeat extends AbstractEntity {

	private static final long serialVersionUID = -4852554918247989599L;

	@Enumerated(EnumType.STRING)
	@Column(name = "repeat_type")
	private RepeatType type;

	@Temporal(TemporalType.DATE)
	private Date start;

	@Embedded
	private RepeatEnd end;

	private int frequency = 1;

	@ElementCollection
	@Enumerated(EnumType.STRING)
	@JoinTable(name = "repeat_point_type", joinColumns = @JoinColumn(name = "repeat_id"))
	private List<RepeatPointType> points = new ArrayList<RepeatPointType>();

	// 摘要
	private String summary;

	@SuppressWarnings("unchecked")
	public List<Date> arrangement() {
		List<Date> results = new ArrayList<Date>();

		Calendar start = createCalendar(this.start);

		whenInWeek();

		Map<String, Object> map = preTreament(start);
		if (map != null) {
			start = (Calendar) map.get("cal");
			results.addAll((List<Date>) map.get("dates"));
		}
		while (start.compareTo(whenEnd()) <= 0) {
			if (hasPoints()) {
				results.addAll(withPoints(start, this.points));
			} else {
				results.add(start.getTime());
			}
			start = rollUp(start);
		}

		return results;
	}

	private Calendar whenEnd() {
		Calendar start = getStartCal();
		Calendar result = null;
		if (RepeatEndType.COUNT == end.getType()) {
			start.add(roleType(), this.frequency * (end.getCount() - 1));
			result = createCalendar(start.getTime());
		} else {
			result = createCalendar(end.getEndDate());
		}
		return result;
	}

	// 频率的操作
	private int roleType() {
		int separated = 0;
		if (RepeatType.EVERYDAY == this.type) {
			separated = Calendar.DATE;
		} else if (RepeatType.EVERYYEAR == this.type) {
			separated = Calendar.YEAR;
		} else if (RepeatType.EVERYMONTH == this.type) {
			separated = Calendar.MONTH;
		} else {
			separated = Calendar.WEEK_OF_MONTH;
		}
		return separated;
	}

	private List<Date> withPoints(Calendar begin, List<RepeatPointType> points) {
		List<Date> results = new ArrayList<Date>();
		Calendar cal = createCalendar(begin.getTime());
		// cal.setFirstDayOfWeek(Calendar.MONDAY);
		// 月节点
		if (hasMonthPoint(points)) {
			if (points.get(0) == RepeatPointType.ONEOFMONTH) {
				cal.set(Calendar.DAY_OF_MONTH, createCalendar(this.start).get(Calendar.DAY_OF_MONTH));
				if (cal.compareTo(whenEnd()) < 0) {
					results.add(cal.getTime());
				}
			} else {
				int weekNum = getDayOfWeekInMonth();
				int week = createCalendar(this.start).get(Calendar.DAY_OF_WEEK);
				cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, weekNum);
				System.out.println("DAY_OF_WEEK_IN_MONTH  " + cal.getTime());
				cal.set(Calendar.DAY_OF_WEEK, week);
				System.out.println("DAY_OF_WEEK_IN_MONTH  " + cal.getTime());
				if (cal.compareTo(whenEnd()) <= 0) {
					results.add(cal.getTime());
				}
			}
			// 周节点
		} else {
			Calendar calb = rollOne(cal);
			while (cal.compareTo(calb) <= 0) {
				if (points.contains(locateWeek(cal))) {
					results.add(cal.getTime());
				}
				cal = rollOneDay(cal);
			}
		}
		return results;
	}

	private boolean hasMonthPoint(List<RepeatPointType> points) {
		return points.contains(RepeatPointType.ONEOFMONTH) || points.contains(RepeatPointType.ONEOFWEEK);
	}

	private boolean hasPoints() {
		if (this.points == null || this.points.isEmpty()) {
			return false;
		}
		return true;
	}

	private Calendar getStartCal() {
		return createCalendar(this.start);
	}

	private Calendar createCalendar(Date date) {
		Calendar result = Calendar.getInstance();
		result.setTime(date);
		return result;
	}

	private void whenInWeek() {
		if (RepeatType.EVERYWORKINGDAYS == this.type) {
			this.points.add(RepeatPointType.MONDAY);
			this.points.add(RepeatPointType.TUESDAY);
			this.points.add(RepeatPointType.WEDNESDAY);
			this.points.add(RepeatPointType.THURSDAY);
			this.points.add(RepeatPointType.FRIDAY);
			this.type = RepeatType.EVERYWEEK;
		} else if (RepeatType.EVERYEVENDAYS == this.type) {
			this.points.add(RepeatPointType.TUESDAY);
			this.points.add(RepeatPointType.THURSDAY);
			this.type = RepeatType.EVERYWEEK;
		} else if (RepeatType.EVERYODDDAYS == this.type) {
			this.points.add(RepeatPointType.MONDAY);
			this.points.add(RepeatPointType.WEDNESDAY);
			this.points.add(RepeatPointType.FRIDAY);
			this.type = RepeatType.EVERYWEEK;
		}
	}

	private Map<String, Object> preTreament(Calendar start) {
		Map<String, Object> map = new HashMap<String, Object>();
		Calendar cal = createCalendar(start.getTime());
		// cal.setFirstDayOfWeek(Calendar.MONDAY);
		List<Date> dates = new ArrayList<Date>();
		if (RepeatType.EVERYWEEK == this.type && !hasMonthPoint(this.points)) {
			Calendar boundary = createCalendar(start.getTime());
			boundary.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY);
			while (cal.compareTo(boundary) < 0) {
				if (points.contains(locateWeek(cal))) {
					dates.add(cal.getTime());
				}
				cal = rollOneDay(cal);
			}
			cal.add(Calendar.DATE, 1);
		} else if (RepeatType.EVERYMONTH == this.type && hasMonthPoint(this.points)) {
			cal.set(Calendar.DAY_OF_MONTH, getMonthLastDay(cal));
			cal.add(Calendar.DAY_OF_MONTH, 1);
			dates.add(start.getTime());
		}

		map.put("cal", this.frequency > 1 ? rollUp(cal) : cal);
		map.put("dates", dates);
		if (map.isEmpty()) {
			return null;
		}
		return map;
	}

	private RepeatPointType locateWeek(Calendar time) {
		return RepeatPointType.getStrBy(time.get(Calendar.DAY_OF_WEEK) - 1);
	}

	private int getMonthLastDay(Calendar cal) {
		Calendar cal1 = createCalendar(cal.getTime());
		cal1.set(Calendar.DATE, 1);// 把日期设置为当月第一天
		cal1.roll(Calendar.DATE, -1);// 日期回滚一天，也就是最后一天
		int maxDate = cal1.get(Calendar.DATE);
		return maxDate;
	}

	private int getDayOfWeekInMonth() {
		return createCalendar(this.start).get(Calendar.DAY_OF_WEEK_IN_MONTH);
	}

	private Calendar rollUp(Calendar start) {
		Calendar result = createCalendar(start.getTime());
		result.add(roleType(), this.frequency);
		return result;
	}

	private Calendar rollOneDay(Calendar cal) {
		Calendar result = createCalendar(cal.getTime());
		result.add(Calendar.DATE, 1);
		return result;
	}

	private Calendar rollOne(Calendar cal) {
		Calendar result = createCalendar(cal.getTime());
		result.add(roleType(), 1);
		return result;
	}

	@Override
	public int hashCode() {
		return new HashCodeBuilder(17, 37).append(this.getId()).toHashCode();
	}

	@Override
	public boolean equals(Object other) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return summary;
	}

	public Date getStart() {
		return start;
	}

	public void setStart(Date start) {
		this.start = start;
	}

	public RepeatEnd getEnd() {
		return end;
	}

	public void setEnd(RepeatEnd end) {
		this.end = end;
	}

	public int getFrequency() {
		return frequency;
	}

	public void setFrequency(int frequency) {
		this.frequency = frequency;
	}

	public List<RepeatPointType> getPoints() {
		return points;
	}

	public void setPoints(List<RepeatPointType> points) {
		this.points = points;
	}

	public String getSummary() {
		return summary;
	}

	public void setSummary(String summary) {
		this.summary = summary;
	}

	public RepeatType getType() {
		return type;
	}

	public void setType(RepeatType type) {
		this.type = type;
	}

}
